<?php
/**
 * Created by sublime 3.
 * @author inhere
 * Date: 14-9-28
 * Time: 10:35
 * Uesd: 主要功能是 hi
 */

namespace ulue\core\controller;

use Ioc;
use Ulue,
    Debug;
use ulue\libs\front\Html;
use ulue\core\router\Uri;
use ulue\core\action\Action;
use ulue\core\view\AbstractView;
use ulue\web\Model;
use ulue\web\Module;
use ulue\web\View;
use ulue\core\abstracts\AbstractBase;
use ulue\libs\helpers\StringHelper;

class AbstractController extends AbstractBase
{
    /**
     * 将要执行的方法信息存储实例
     * @var Action
     */
    private $action;

    /**
     * 若是自定义了操作绑定类，存储绑定的类id --- 含命名空间的类名
     * @var string
     */
    private $actionBindClassId;

    /**
     * 执行动作绑定到action类的 的实例
     * @var null|AbstractActionController
     */
    private $actionController;

    /**
     * 渲染后的数据
     * @var null|string
     */
    private  $output     = null;

    /**
     * @var string 'index'
     */
    public $defaultAction = 'index';

    /**
     * @var string
     */
    protected $actionSuffix = 'Action';

    /**
     * 操作是否全部绑定到类 配置设置 或者 actionBindToClass() 返回 true
     * @var bool
     */
    protected $allBindToClass = false;

    /**
     * 覆盖 init() 时,请记得先 调用 parent::init(); 以防止出现错误
     * @return void
     */
    protected function init()
    {
        // 若控制器中没有设置 defaultAction 等属性,则读取 Module 中配置的默认值
        !$this->defaultAction && $this->defaultAction = Ulue::$app->module->defaultAction;
        !$this->actionSuffix  && $this->setActionSuffix( Ulue::$app->getSuffix('action') );
    }

    /**
     * filters 过滤请求
     * @inherit AbstractBase::filters()
     * @return array
     */
    public function filters()
    {
        /*
        return [
          'verbs'  => 'GET,POST, ...',
          # Only http & https
          'scheme'  =>  'https',
          'post'    =>  '80',
          'sslPort' =>  '443',
        ]
         */
        return [];
    }

    /**
     * [accessRules 访问权限、角色控制]
     * @return array
     */
    public function accessRules()
    {
        /*
        return [
          'method'  => 'GET,POST, ...',
          'allow' => 'method1,method2, ...',
          'users' => 'user1,user2, ...',
          'roles' => 'admin,editor, ...',
        ]
         */
        return [];
    }

    public function accessValidate()
    {
        //$accessRules = $this->accessRules();
    }

    public function executeFilter()
    {
        //$accessRules = $this->accessRules();
    }

################################ execute action method ################################

    /**
     * 1.每次控制器文件运行run()时会自动先执行 beforeExecute()，即是执行当前控制器下的所有方法前都会先执行 beforeRun()
     * 2.若仅需在某一些方法执行前需要自动执行 beforeExecute(),请在 setBeforeExecute()中放入需要先执行autoRun()的 方法列表
     * 可用于事先的 验证（用户验证） 及 获取公共数据（导航栏）
     * @return bool
     */
    protected function beforeExecute()
    {
        return true;
    }

    /**
     * 在执行前做一些检查准备工作 todo 允许更改动作名
     * @param $actionName
     * @throws \DRuntimeException
     */
    protected function prepareExecute( &$actionName )
    {
    }

    // public function execute() {}

    protected function afterExecute( & $output )
    {
        // ...
    }

    /**
     * 得到请求方法的参数信息，对传入参数数组 $queryParams 根据 方法的参数名 调整 $queryParams 的key顺序，
     * 便于在 调用方法时(call_user_fun_array) 按正确的顺序 映射传入的参数
     * @param  AbstractController $object 当前的控制器对象
     * @param  string $theActionMethod 请求的方法， 绑定到类时 则固定为 'execute'
     * @param  array $queryParams 请求传入的参数
     * @throws \DInvalidArgumentException
     * @return bool|array
     */
    protected function getMethodParameters(AbstractController $object, $theActionMethod, array $queryParams)
    {
        if ( !$queryParams ) {
            return false;
        }

        $reflectMethod = new \ReflectionMethod($object,$theActionMethod);
        $parameters    = $reflectMethod->getParameters();
        $actionParams  = [];

        foreach ($parameters as $p) {
            if ( isset($queryParams[$p->name]) && $queryParams[$p->name] !== '') {
                $actionParams[$p->name] = $queryParams[$p->name];
            } else if ( $p->isOptional() ) {
                $actionParams[$p->name] = $p->getDefaultValue();
            } else {
                throw new \DInvalidArgumentException(Ulue::tl('{method}() of the {pos} param [${name}] is required, please pass in parameter values',[
                    '{method}' => $theActionMethod,
                    '{pos}'    => $p->getPosition(),
                    '{name}'   => $p->name,
                    ])
                );
            }
        }

        return $actionParams;
    }

    /**
     * 根据解析后的动作路由，保存要访问的控制器动作(方法)信息
     * @param  string     $aRoute
     * @return void
     */
    public function activeAction($aRoute)
    {
        $name = $aRoute ? : $this->defaultAction;

        if ( strpos($name, '/') ) {
            list($name, $queryStr) = explode('/', $name, 2);
        }

        if ( !$name ) {
            throw new \DLogicException( 'Action name is not set to be executed!');
        }

        $options = [
            'name'       => $name,
            'suffix'     => $this->actionSuffix,
            'methodName' => $name . $this->actionSuffix,
            'route'      => $this->route . '/' . $name,
            'id'         =>  self::fullName() . ':' . $name,
        ];

        $this->setAction(new Action($options) );

        if (isset($queryStr)) {
            Ulue::$app->get('router')->addParamsByPath($queryStr);
        }
    }


################################## action bound to class ##################################

    /**
     * 设置操作绑定到类, 默认为 false 操作都在控制器内处理
     *
     * 1.--设置为 true 后 当前控制器内 所有可访问操作
     * 将默认绑定到 module/controllers/{controllerName}/{actionName}Action.php
     * 等同于设置 @see $allBindToClass = true;
     * 仍可在控制器内用 public function init(){} 在访问操作类前做一些初始化工作[e.g. 用户验证等]
     * @example index.php/site/about
     *     app/controllers/site/AboutAction.php
     *
     * 2.--要单独将某个操作绑定到类 则设置 具体的操作
     * @example
     * return ['add', 'edit']
     * 对应action class:
     *    'add'  -->  ../{controllerName}/AddAction.php
     *    'edit' -->  ../{controllerName}/EditAction.php
     *
     * 3.--更细化的设置，指明绑定类 类名会存储到 @see $actionBindClassId
     *  没有命名空间时 类文件存在指向同 1. 2. 即是 控制器同名文件夹内
     *  有命名空间 则按设定的命名空间查找，请输入完善的类命名空间
     * return [
     *    'add'    => ['class'=>'MyAddAction'],
     *    'edit'   => ['class'=>'MyEditAction'],
     *    'delete' => ['class'=>'namespace\MyDeleteAction'],
     * ]
     * 注：如果设置了绑定到类，但控制器内仍然写了 同名动作方法，则会优先控制器内的 方法，而略过绑定。即此时绑定无效
     */
    protected function actionBindToClass()
    {
        return false;
        //return ['action1', 'action2'];
    }

    /**
     * 设置当前控制器的全部操作(动作)是否绑定到 action类
     * @see $allBindToClass
     * @param bool $value
     * @return $this
     */
    public function setAllBindToClass($value=true)
    {
        $this->allBindToClass = (bool)$value;

        return  $this;
    }


    /**
     * Gets the 若是自定义了操作绑定类，存储绑定的类id --- 含命名空间的类名.
     *
     * @return string
     */
    public function getActionBindClassId()
    {
        return $this->actionBindClassId;
    }

    /**
     * Sets the 若是自定义了操作绑定类，存储绑定的类id --- 含命名空间的类名.
     *
     * @param string $actionBindClassId the action bind class id
     *
     * @return self
     */
    protected function setActionBindClassId($actionBindClassId)
    {
        $this->actionBindClassId = $actionBindClassId;

        return $this;
    }

    /**
     * 检查动作是否设置了绑定到类
     * @param $actionName
     * @return bool
     */
    protected function checkActionIsBindToClass($actionName)
    {
        /**
         * 已设置了全部绑定。
         */
        if ((bool) $this->allBindToClass) {
            return true;
        }

        $bindSetting = $this->actionBindToClass();

        // 针对整个控制器设置了 true | false
        if (is_bool($bindSetting) || in_array($bindSetting, [0,1], true) ) {
            return ($this->allBindToClass = (bool)$bindSetting);
        }

        // 为空 '' null []
        if (!$bindSetting) {
            return false;
        }

        if (is_string($bindSetting)) {
            $bindSetting = trim($bindSetting, ', ');
            $bindSetting = !strpos($bindSetting, ',' ) ? [$bindSetting]: explode(',', $bindSetting);
        }
        // filter
        $bindSetting = array_filter($bindSetting, 'trim');

        // 先检查指定了 绑定类名的 操作 ['add' => 'MyAddAction' ]
        if (isset($bindSetting[$actionName]) && $bindClass = $bindSetting[$actionName]) {
            $bindClass = trim($bindClass, '\\ ');

            $this->actionBindClassId = strpos($bindClass,'\\')>1 ?
                $bindClass :
                dirname($this->id).'\\'.$this->name.'\\'.$bindClass;

            return true;
        }

        return in_array($actionName, $bindSetting);
    }

################################ response data control method ################################

    /**
     * @param $name
     * @param $value
     * @param bool $replace
     * @return $this
     */
    public function setHeader($name, $value, $replace=false)
    {
        $this->response->setHeader($name, $value, $replace);

        return $this;
    }

    /**
     * @return $this
     */
    public function clearHeaders()
    {
        $this->response->clearHeaders();

        return $this;
    }

    /**
     * @param $headers
     * @return $this
     */
    public function setHeaders($headers)
    {
        $this->response->setHeaders($headers);

        return $this;
    }

    /**
     * @return array
     */
    public function getHeaders()
    {
        return $this->response->getHeaders();
    }

    /**
     * @param $charset
     * @return $this
     */
    public function setCharset($charset)
    {
        $this->response->setCharset($charset);

        return $this;
    }

    /**
     * @return string
     */
    public function getCharset()
    {
        return $this->response->getCharset();
    }

    /**
     * @param $content
     * @return $this
     */
    public function setBody($content)
    {
        $this->response->setBody($content);

        return $this;
    }

    /**
     * @param bool $toArray
     * @return array|null|string
     */
    public function getBody($toArray=false)
    {
        return $this->response->getBody($toArray);
    }

    /**
     * getMimeType
     * @return  string
     */
    public function getMimeType()
    {
        return $this->response->getMimeType();
    }

    /**
     * setMimeType
     * @param   string $mimeType
     * @return  static  Return self to support chaining.
     */
    public function setMimeType($mimeType)
    {
        $this->response->setMimeType($mimeType);

        return $this;
    }

################################ helper method ################################

    /**
     *
     * $data = [
     *    'status'  => 1 , // int 0 error 1 success 2 unknown
     *    'msg'     => 'string' , // message
     *    'output'  => mixed , // output data
     * ]
     * @param bool $exit Whether the program exit
     *
     */
    public function outJson($data, $exit=true)
    {
        $data = @json_encode((array)$data);

        if ( (bool)$exit ) {
            exit($data);
        }

        echo $data;
    }

    public function toJson($data)
    {
        return @json_encode((array)$data);
    }

    /**
     * 可直接实例化模型 ，也可使用 $this->model()
     * @param $modelClass
     * @return bool|Model
     */
    public function model($modelClass)
    {
        return new $modelClass;
    }

    public function redirect($url, $time = 0, $msg = null)
    {}

    //操作成功后跳转，参数请看 handleJumpPage()
    public function doneJump($url, array $options=[], $useLayout=true)
    {
        return $this->successJump($url, $options, $useLayout);
    }

    //操作成功后跳转，参数请看 handleJumpPage()
    public function successJump($url, array $options=[], $useLayout=true)
    {

        return $this->handleJumpPage($url, $options, $useLayout);
    }

    //操作失败后跳转，参数请看 handleJumpPage()
    public function errorJump($url, array $options=[], $useLayout=false)
    {
        $options = array_merge( [
            'msg'       => '操作失败啦！',
            'time'      => '3',
            'btnText'   => '立即跳转',
            'viewFile'  => Ulue::$app->get('pages.error_jump'),
        ], $options);

        return $this->handleJumpPage($url, $options, $useLayout);
    }

    /**
     * 带提示的跳转
     * @param $url string  url addr
     * @param array $options 选项配置
     * 如果第一个参数是数组，则认为用数组配置传入参数
     * [
     *     'msg'       => '操作提示信息',
     *     'time'      => '倒计时',
     *     'btnText'  => '按钮文字',
     *     'viewFile' => '视图文件',
     * ]
     * @param  bool $useLayout 使用布局
     * @return string
     */
    protected function handleJumpPage($url, array $options=[], $useLayout=true)
    {
        $url = $url ? : \Ioc::get('app.uri')->get('index.path');

        $default = [
            'msg'       => '操作成功啦！',
            'time'      => '3',
            'btnText'   => '立即跳转',
            'viewFile'  => Ulue::$app->get('pages.done_jump'),
        ];

        $uriService = \Ioc::get('app.url');
        $url        = $uriService::to($url);
        $options    = array_merge($default, array_filter($options));
        $viewFile   = $options['viewFile'];
        $viewFile   = Ulue::resolveAlias($viewFile);

        if ($options['time'] == 0) {
            header('location:'.$url);
        }

        if (!file_exists($viewFile)) {
            \Trigger::error('文件：'.$viewFile.'不存在，请设置并检查跳转需要的视图文件！');
        }

        $options['url'] = $url;
        unset($options['viewFile']);

        if ( (bool)$useLayout ) {
            return $this->display($viewFile, $options);
        }

        return $this->showPartial($viewFile, $options);
    }

    public function getContainer()
    {
        return Ulue::$app->getContainer();
    }

    /**
     * @return string
     */
    public function getActionSuffix()
    {
        return $this->actionSuffix;
    }

    /**
     * @param string $actionSuffix
     * @return $this
     */
    public function setActionSuffix($actionSuffix)
    {
        $this->actionSuffix = $actionSuffix;

        return $this;
    }

    /**
     * Set the well execute's action method name;
     * @param $value
     */
    public function setActionName($value)
    {
        $this->action->name=$value;
    }

    /**
     * @return string
     */
    public function getActionName()
    {
        return $this->action->name;
    }

    /**
     * @return string
     */
    public function getOutput()
    {
        return $this->output;
    }

    /**
     * @return string
     */
    protected function setOutput($output)
    {
        $this->output = $output;

        return $this;
    }

    /**
     * @param $name
     * @param $args
     * @return mixed|void
     * @throws \DUnknownCalledException
     */
    public function __call($name,$args)
    {
        parent::__call($name,$args);
    }

    public function __set($name,$value)
    {
        parent::__set($name,$value);
    }

    /**
     * @param $name
     * @return mixed
     * @throws \ulue\core\TheSystemException
     */
    public function __get($name)
    {
        return parent::__get($name);
    }



    /**
     * Gets the 执行动作绑定到action类的 的实例.
     *
     * @return null|AbstractActionController
     */
    public function getActionController()
    {
        return $this->actionController;
    }

    /**
     * Sets the 执行动作绑定到action类的 的实例.
     *
     * @param null|AbstractActionController $actionController the action controller
     *
     * @return self
     */
    protected function setActionController(AbstractActionController $actionController)
    {
        $this->actionController = $actionController;

        return $this;
    }

    /**
     * Gets the value of action.
     *
     * @return Action
     */
    public function getAction()
    {
        return $this->action;
    }

    /**
     * Sets the value of action.
     *
     * @param Action $action the action
     * @return self
     */
    public function setAction(Action $action)
    {
        $this->action = $action;

        return $this;
    }

}